<?php
/**
 ***********************************************************************************************************************
 * 访问控制系统服务端：
 * ① 提供1分钟、5分钟、15分钟、1小时、6小时、12小时、1天，7个指标的访问量控制
 * ② 客户端可以自己决定自己处理触发规则的用户访问控制，或使用通用控制。
 * @author llq
 ***********************************************************************************************************************
 */
namespace soa\access;
use artisan\config;
use artisan\input;

/**
 * Class access
 * @package soa\access
 */
class access
{
    /**
     * @var string 当前使用访问控制的服务名称 在/kdhelp/config/access.php中进行配置
     */
    private $service;

    /**
     * @var array 当前使用访问控制的服务的配置 在/kdhelp/config/access.php中进行配置
     */
    private $conf;

    /**
     * @var string 当前访问IP
     */
    private $ip;

    /**
     * @var
     */
    private $useragent;

    /**
     * @var array 当前用户的访问记录（包含本次访问信息）
     */
    private $record;

    /**
     * @var array 当前管理员设置的访问规则
     */
    private $rules;

    /**
     * @var array 当前IP是否存在于黑白名单中
     */
    private $black_white;

    /**
     * @var
     */
    private $account;

    /**
     * @var bool
     */
    private $enable = false;

    /**
     * @var string
     */
    private $block_type = 'black';

    /**
     * @var int
     */
    private $rule_limit = 0;

    /**
     * @var string
     */
    private $rule_index = '';

    /**
     * 所有时段指标
     * @var array
     */
    private $indexes = [
        // 一分钟
        'one_minute',
        // 五分钟
        'five_minute',
        // 十五分钟
        'fifteen_minute',
        // 一小时
        'one_hour',
        // 六小时
        'six_hour',
        // 十二小时
        'twelve_hour',
        // 一天
        'one_day',
    ];

    /**
     * 传入需要控制访问的服务名称,并且初始化该服务的访问控制
     * @param string $service
     */
    public function __construct($service)
    {
        $this->service = $service;
    }


    /**
     * 设置当前控制的服务名称
     * @param $service
     */
    public function setService($service)
    {
        $this->service = $service;
    }

    /**
     * 获取当前控制的服务名称
     * @return string
     */
    public function getService()
    {
        return $this->service;
    }

    /**
     * 设置需要控制的账号名称 可以是userid\parter_name等可以唯一标识的指标字段
     * @param $account
     */
    public function setAccount($account)
    {
        $this->account = $account;
    }

    /**
     * 获取当前账号名称
     * @return mixed
     */
    public function getAccount()
    {
        return $this->account;
    }

    /**
     * 设置IP，不设置使用默认IP 注：提供一个客户端自定义IP功能
     * @param $ip
     */
    public function setIp($ip)
    {
        $this->ip = $ip;
    }

    /**
     * 获取当前IP
     * @return string
     */
    public function getIp()
    {
        return empty($this->ip) ? input::ip() : $this->ip;
    }

    /**
     * 设置当前useragent
     * @param $useragent
     */
    public function setUserAgent($useragent)
    {
        $this->useragent = $useragent;
    }

    /**
     * 获取当前useragent
     * @return mixed
     */
    public function getUserAgent()
    {
        return empty($this->useragent) ? input::userAgent() : $this->useragent;
    }

    /**
     * 记录危险访问 由客户端手动操作 系统默认不做处理
     */
    public function addDanger()
    {
        $this->getIndexBase() == 'ip' ?
            $this->getDangerModel()->addIp($this->service, $this->getIp(), $this->record) :
            $this->getDangerModel()->addAccount($this->service, $this->getAccount(), array_merge($this->record, ['ip' => $this->getIp()]));
    }


    /**
     * 获取limit信息
     * @return array
     */
    public function getCheckInfo()
    {
        return [
            // 触发的规则
            'rule_index' => $this->rule_index,
            // 规则允许请求的次数
            'rule_limit' => $this->rule_limit,
            // 用户访问信息记录
            'user_request_record' => $this->record
        ];
    }

    /**
     * 输出配置文件中设置的访问信息 仅当调用时才会输出 默认将控制权留给客户端自己定义如何输出
     */
    public function render()
    {
        exit(empty($this->conf['block_response'][$this->block_type]) ? '' : (string)$this->conf['block_response'][$this->block_type]);
    }


    /**
     * 启动访问控制
     * @return array
     */
    public function check()
    {
//        if($this->service == 'captcha_sms_service'){
//            logDebug($this->getIp(),"register_ip",'1');
//        }

        // 初始检查
        if(!$this->initCheck()) {
            return true;
        }
	//logDebug('1', 'check');

  //      if($this->service == 'captcha_sms_service'){
  //          logDebug($this->getIp(),"register_ip",'2');
  //      }


        // 黑名单检查
        if($this->blackCheck()) {
            $this->render();
        }
	//logDebug('2', 'check');
   //     if($this->service == 'captcha_sms_service'){
   //         logDebug($this->getIp(),"register_ip",'3');
   //     }

        // 白名单检查
        if($this->whiteCheck()) {
            return true;
        }
	//logDebug('3', 'check');
   //     if($this->service == 'captcha_sms_service'){
   //         logDebug($this->getIp(),"register_ip",'4');
   //     }

        // 访问规则检查
        if(!$this->ruleCheck()) {
            return false;
        }
	//logDebug('4', 'check');
   //     if($this->service == 'captcha_sms_service'){
   //         logDebug($this->getIp(),"register_ip",'5');
   //     }


        // 刷新访问记录
        $this->getIndexBase() == 'ip' ?
            $this->getRecordModel()->refreshByIp($this->service, $this->getIp()) :
            $this->getRecordModel()->refreshByAccount($this->service, $this->getAccount());
	//logDebug('5', 'check');
        return true;
    }

    /**
     * 初始化检查
     * @return bool
     */
    private function initCheck()
    {
        // 服务检查
        if(
            empty($this->getService()) ||
            !is_string($this->getService()) ||
            empty($conf = config::get('access', 'rule')) ||
            empty($conf[$this->getService()])
        ) {
            return false;
        }

        $this->conf = $conf[$this->getService()];

        // 如果开启访问控制继续检查
        if(!empty($this->conf['enable'])) {
            $this->enable = $this->conf['enable'];

            // 配置文件检查
            if(empty($this->conf['limit'])) {
                return false;
            }

            // IP检查
            if(
                empty($this->getIp()) ||
                !is_string($this->getIp()) ||
                !preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $this->getIp())
            ) {
                $this->block_type = 'unknow_ip';
                // 如果开启了屏蔽未知IP 直接屏蔽
                if(!empty($this->conf['block_unknow_ip'])) {
                    $this->render();
                }
                return false;
            }

            // useragent检查
            if(empty($this->getUserAgent()) || !is_string($this->getUserAgent())) {
                $this->block_type = 'unknow_useragent';
                // 如果开启了屏蔽未知IP 直接屏蔽
                if(!empty($this->conf['block_unknow_useragent'])) {
                    $this->render();
                }
                return false;
            }

            // 自定义账号检查
            if(!empty($this->getAccount()) && !is_string($this->getAccount())) {
                return false;
            }

        }

        return true;
    }

    /**
     * 黑名单检查
     * @return bool
     */
    private function blackCheck()
    {
        $this->black_white = $this->getRuleModel()->isBlackWhite($this->getIp());
        if(!empty($this->black_white['is_black'])) {
            $this->block_type = 'black';
            return true;
        }
        return false;
    }

    /**
     * 白名单检查
     * @return bool
     */
    private function whiteCheck()
    {
        if(!empty($this->black_white['is_white'])) {
            return true;
        }
        return false;
    }


    private function ruleCheck()
    {
        //if($this->service == 'captcha_sms_service'){
        //    logDebug($this->getIp(),"register_ip");
        //}
        // 组织用户访问记录 包括当前次
        $this->record = $this->getIndexBase() == 'ip' ?
            $this->getRecordModel()->organizeByIp($this->service, $this->getIp()) :
            $this->getRecordModel()->organizeByAccount($this->service, $this->getAccount());
	//logDebug($this->service.' '.$this->getIndexBase().' '.$this->getIp(),'check', 'a');
        // 获取访问规则
        $this->rules = $this->getIndexBase() == 'ip' ?
            $this->getRuleModel()->getRuleByIp($this->service, $this->getIp()) :
            $this->getRuleModel()->getRuleByAccount($this->service, $this->getAccount());
	//logDebug($this->rules, 'check', 'b');
        // 来自后台的规则检查
        if(!empty($this->rules)) {
            foreach($this->rules as $rule_index => $limit) {
                if(in_array($rule_index, $this->indexes) && $this->record[$rule_index] >= $limit) {
                    $this->rule_limit = $limit;
                    $this->rule_index = $rule_index;
                    $this->block_type = 'limit';
	//	    logDebug($rule_index.' '.$limit.' ##', 'register', $this->record[$rule_index]);
                    return false;
                }
            }
            // 如果未超过后台设置访问量 跳过
            return true;
        }
	//logDebug($this->record, 'check', '1');

        // 配置文件中的规则检查
        if(!empty($this->conf['limit'])) {
            foreach($this->conf['limit'] as $rule_index => $limit) {
                if(in_array($rule_index, $this->indexes) && $this->record[$rule_index] >= $limit) {
                    $this->rule_limit = $limit;
                    $this->rule_index = $rule_index;
                    $this->block_type = 'limit';
	//	    logDebug($rule_index.' '.$limit.' ###', 'register', $this->record[$rule_index]);
                    return false;
                }
            }
        }
	//	        logDebug($this->record, 'check', '2');

        return true;
    }

    /**
     * 获取记录索引
     * @return string
     */
    private function getIndexBase()
    {
        return !empty($this->getAccount()) && is_string($this->getAccount()) ? 'account' : 'ip';
    }

    /**
     * @var \soa\access\model\redis\record
     */
    private $recordModel;

    /**
     * @return \soa\access\model\redis\record
     */
    private function getRecordModel()
    {
        if(empty($this->recordModel)) {
            $this->recordModel = new \soa\access\model\redis\record($this->service);
        }
        return $this->recordModel;
    }

    /**
     * @var \soa\access\model\redis\rule
     */
    private $ruleModel;

    /**
     * @return \soa\access\model\redis\rule
     */
    private function getRuleModel()
    {
        if(empty($this->ruleModel)) {
            $this->ruleModel = new \soa\access\model\redis\rule($this->service);
        }
        return $this->ruleModel;
    }

    /**
     * @var \soa\access\model\redis\danger
     */
    private $dangerModel;

    /**
     * @return \soa\access\model\redis\danger
     */
    private function getDangerModel()
    {
        if(empty($this->dangerModel)) {
            $this->dangerModel = new \soa\access\model\redis\danger($this->service);
        }
        return $this->dangerModel;
    }

}
